iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 3
1
自我挑戰組

Android Architecture 及 Unit Test系列 第 3

[Day 3] Kotlin Coroutines:Part 1 Basic

  • 分享至 

  • xImage
  •  

Kotlin Coroutines 是 Kotlin 提供的一個支援協程的 Library ,讓開發者有另一種方式處理非同步問題。

關於協程的概念我不再贅述,網路上或是官網都有對此概念有更詳盡的說明,未來有機會可能會在挑戰後單獨開一篇分享,因為事實上我對於 Coroutines 本質的了解現在也還沒到可以分享的地步。讓我在這次挑戰使用 Coroutines 是因為我曾經在某篇文章看過一個結論:

有了協程,你的異步程式看起來就像同步程式一樣

Coroutines 是為了要解決什麼事?

試想一個用戶登入的情境:

取得一個 token 後,再用這個 token 請求登入 API ,登入成功後再獲得一些用戶基本資料,接著顯示登入成功。

class ViewModel {

    fun getToken(): Token {
        // 做一些 API 耗時操作
        return token 請求 access token
    }
    
    fun verify(token, userName): Boolean {
        // 做一些 API 耗時操作
        return result // 登入成功或失敗
    }
    
    fun requestAndSaveUserData(userName): {
        // 做一些 API 耗時操作
        return Success // 取得一些登入者的資料
    }
    
    ......
    
    fun login(userName: String) {
        val token = getToken()
        val result = verify(token, userName)
        if (result) {
            val success = requestAndSaveUserData(userName)
            if (success) {
                showLoginSuccess("Success")
            }
        }
    }
}

所有函數都是耗時操作,因此不能直接在 UI 線程執行,同時每一個方法都依賴前一個方法的結果,三者都不能同時進行,那要怎麼辦呢?

Callback (Lambda)

第一種作法是使用 Callback ,在 Kotlin 中也可以使用 Lamnda Function :

fun getTokenAsync(action: (Token) -> Unit) {
    ......
}

fun verifyAsync(
    token: Token,
    userName: String,
    action: (Boolean) -> Unit
) {
    ......
}

fun requestAndSaveUserDataAsync(
    userName: String, 
    result: (Result) -> Unit
) {
    ......
}

......

fun login(userName: String) {
    getTokenAsync { token ->
        verifyAsync(token, userName) { verifyResult ->
            requestAndSaveUserDataAsync(userName) { result ->
                showLoginSuccess("Success")
            }
        }
    }
}

...看起來很不舒服,對吧?

在一些簡單的情境使用 Callback 可以很好的處理問題,但是面對比較複雜的狀況時就很容易發生 "Call Hell" 了,而且也很難處理異常狀況。

Rx

接下來看看另一種十分熱門的做法,RxJava 的鏈式調用:

fun getToken(): Observable<Token> { ...... }

fun verify(userName): Observable<Boolean> {
    return getToken()
            .flatMap { token ->
                ......
            }
}

fun requestAndSaveUserData(userName: String): Observable<Result> {
    return verify(userName)
            .flatMap { ...... }
}

fun login(userName: String) {
    requestAndSaveUser(userName)
        .subscribeOn(Schedulers.io())
        .observerOn(AndroidSchedulers.mainThread())
        .subscribe({
            showLoginSuccess(it)
        }, {
            it.printStackTrace()
        })
}

RxJava 豐富的操作符、簡單的線程調度及異常處理,讓大家都很滿意,但前提是你要對他有深入的了解!

許多教學文常常提到 RxJava 讓程式碼變得有條理,但那都是建立在了解 RxJava 及其複雜的操作符之上。昂貴的學習成本讓許多開發著卻步,那是否有更簡單的作法呢?

Coroutines

suspend fun getToken(): Token { ...... }

suspend fun verify(token, userName: String): Boolean { ...... }

suspend fun requestAndSaveUserData(userName: String): Result { ...... }

fun login(userName: String) {
    GlobalScope.launch {
        try {
            val token = getToken()
            val isLogin = verify(token, userName)
            if (isLogin) {
                val success = requestAndSaveUserData(userName)
                if (success) {
                    showLoginSuccess("Success")
                }
            }
            ......
        }
    }
}

是不是簡單很多呢?

Coroutines 讓程式碼變得更加簡潔,同時異常處理也簡單很多,閱讀起來也十分的有條理,看起來跟同步執行幾乎沒有兩樣。

對於 Coroutines,這邊附上我的理解:

Coroutines 允許函式可以被 "掛起" (suspend),避免主線程的佔用,而且被掛起的函式後續也可以被恢復,並從暫停時保留的狀態繼續執行。

再白話一點的說法,以剛剛的例子說明:
在 Main thread 執行到一個耗時的 getToken() 時,由於需要等待 IO thread 的結果,所以先暫停 getToken() ,讓出 Main thread 去做其他的事,等到 IO thread 執行完畢後,再重新繼續 getToken() ,而這種控制 function 的 "暫停" -> "繼續" 的狀態切換,就是 Coroutines 實際上在做的事。

事實上 Coroutines 的概念早在 1960 年代就有了,而且早期的語言如 Lua、Simula ,一直到當今的 C#pythonGolangSwift 等皆相繼引入 Coroutines 的概念,在別的領域裡十分流行,Android 則直到 Kotlin 成為官方語言後才以 Support Library 的方式引入,並在近年流行起來。

Gradle 導入

前面已經提到了,Kotlin 的 Coroutines 是以 Support Library 的方式提供給開發者使用:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1'
}

另外 Kotlin 還提供了 kotlinx-coroutines-android 給 Android 開發者使用,主要多增加了 Dispatchers.Main 讓開發者可以更輕易的切換到 Main thread ,同時也確保在 Android app 崩潰前有確實的紀錄 Log:

// Android 開發者可以只 import 此 library
dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1'
}

這樣就完成 Coroutines 的環境配置,明天再來簡單的練習。


上一篇
[Day 2] 架構以及 Spec
下一篇
[Day 4] Kotlin Coroutines:Part 2 Scope、Suspend & Dispatcher
系列文
Android Architecture 及 Unit Test30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
jiamingla
iT邦新手 4 級 ‧ 2021-05-25 02:10:09

酷喔

我要留言

立即登入留言